// Copyright (c) WanSheng Intelligent Corp. All rights reserved.
// Licensed under the LGPL v3.0 license. 


#include <stdlib.h>
#include <stdbool.h>
#include <linux/prctl.h>
#include "opcua_plugin.h"
#include "wa_plugin_sdk.h"
#include "logs.h"
#include "path.h"
#include "ams_constants.h"
#include "file_util.h"
#include "wa_edge_configs.h"
#include <bitset>
#include <algorithm>

opcua_device* g_opcua_devices[MAX_OPCUA_DEVICE] = { 0 };
wa_plugin_device_info_t* g_devices_info = NULL;

static int get_type_name(const char* type)
{
	WARNING2("Enter..[%s]", type);
	if (strcmp(type, "Bit") == 0)
		return T_Bits;
	else if (strcmp(type, "Int8") == 0)
		return T_int8;
	else if (strcmp(type, "Uint8") == 0)
		return T_Uint8;
	else if (strcmp(type, "Int16") == 0)
		return T_int16;
	else if (strcmp(type, "Uint16") == 0)
		return T_Uint16;
	else if (strcmp(type, "Int32") == 0)
		return T_int32;
	else if (strcmp(type, "Uint32") == 0)
		return T_Uint32;
	else if (strcmp(type, "Int64") == 0)
		return T_int64;
	else if (strcmp(type, "Uint64") == 0)
		return T_Uint64;
	else if (strcmp(type, "Float") == 0)
		return T_Float;
	else if (strcmp(type, "Double") == 0)
		return T_Double;
	else
		return T_Invalid_DataType;
}

opcua_device* find_device(const char* name)
{
	WARNING2("Enter..");
	int i;
	for (i = 0; i < MAX_OPCUA_DEVICE; i++)
	{
		if (g_opcua_devices[i] && strcmp(name, g_opcua_devices[i]->plugin_cfg->device_name) == 0)
		{
			return g_opcua_devices[i];
		}
	}

	return NULL;
}

static opcua_device* parse_customer_config_A(wa_plugin_device_info_t* device_info)
{
	WARNING2("Enter..");

	if (device_info == NULL)
		return NULL;
	if (device_info->custom_cfg == NULL)
		return NULL;
	cJSON* custom_cfg = cJSON_Parse(device_info->custom_cfg);
	if (custom_cfg == NULL)
		return NULL;
	if (!cJSON_IsObject(custom_cfg))
	{
		cJSON_Delete(custom_cfg);
		return NULL;
	}

	const char* URL = cJSONx_GetObjectString(custom_cfg, "URL");

	WARNING2("The PARA:URL[%s]", URL);

	if (URL == NULL)
	{
		WARNING("[%s] URL error!\n", device_info->device_name);
		cJSON_Delete(custom_cfg);
		return NULL;
	}


	opcua_device* opcua_de = (opcua_device*)malloc(sizeof(opcua_device));
	memset(opcua_de, 0, sizeof(opcua_device));

	opcua_de->url = strdup(URL);
	opcua_de->plugin_cfg = device_info;

	cJSON_Delete(custom_cfg);

	return opcua_de;
}

int set_device(opcua_device* device)
{
	WARNING2("Enter..");
	int i;
	for (i = 0; i < MAX_OPCUA_DEVICE; i++)
	{
		if (g_opcua_devices[i] == NULL)
		{
			g_opcua_devices[i] = device;

			return i;
		}
	}

	return -1;
}

bool load_device_config(const char* plugin_name)
{
	WARNING2("Enter..");
	char buf[256];
	int device_num = 0;
	WARNING("Start loading opcua config...");

	assert(g_devices_info == NULL);

	wa_plugin_device_info_t* devices_info = wa_plugin_load_device_info_A(plugin_name, &device_num);

	if (devices_info == NULL)
	{
		WARNING2("load cfg fail");
		return false;
	}

	for (int k = 0; k < device_num; k++)
	{
		wa_plugin_device_info_t* info = &devices_info[k];
		if (find_device(info->device_name))
		{
			WARNING2("device %s already add. skip current.", info->device_name);
			continue;
		}
		WARNING2("bus_info: %p, tcp_addr: %p\n", info->bus_info, &info->bus_info->tcp_addr);
		opcua_device* opcua = parse_customer_config_A(info);
		if (opcua == NULL)
		{
			WARNING2("参数解析失败");
			continue;
		}

		set_device(opcua);
	}

	g_devices_info = devices_info;

	WARNING("Finished loading opcua plugin config\n");

	return true;
}

static char* parse_opcua_cmd_query(char* query, int* NameSpaceIndex, string& NodePath, int* items, int* type)
{
	WARNING2("Enter..[%s]", query);
	char buf[128] = { 0 };
	char name[128] = { 0 };
	int len = strlen(query);
	if (!find_key_value(query, len, "DI", name, sizeof(name), '&'))
	{
		WARNING2("No have DI");
		return NULL;
	}

	if (!find_key_value(query, len, "NameSpaceIndex", buf, sizeof(buf), '&'))
	{
		WARNING2("No have NameSpaceIndex");
		return NULL;
	}
	*NameSpaceIndex = atoi(buf);


	if (!find_key_value(query, len, "NodePath", buf, sizeof(buf), '&'))
	{
		WARNING2("No have NodePath");
		return NULL;
	}
	NodePath = buf;


	if (items)
	{
		if (find_key_value(query, len, "ITEMS", buf, sizeof(buf), '&'))
			*items = atoi(buf);
		else
			*items = 1;
	}


	if (!find_key_value(query, len, "Datatype", buf, sizeof(buf), '&'))
	{
		WARNING2("No have Datatype");
		return NULL;
	}
	*type = get_type_name(buf);

	if ((opcua_dt)*type == T_Invalid_DataType)
	{
		WARNING2("Datatype is invalid");
		return NULL;
	}

	return strdup(name);
}

opcua_device* find_device_open(const char* name)
{
	WARNING2("Enter..");
	opcua_device* device = find_device(name);
	if (device && device->dc)
	{
		return device;
	}

	WARNING2("error");
	return NULL;
}

void opcua_Get(restful_request_t* request, REQ_ENV_HANDLE req_env)
{
	WARNING2("Enter..");
	cJSON* device_data;
	const char* payload;
	UA_StatusCode res;

	if (request->query == NULL)
	{
		WARNING2("query is NULL");
		return;
	}

	int NameSpaceIndex, items = 0;
	string NodePath;
	int type = T_Invalid_DataType;

	opcua_device* device;
	char* name = parse_opcua_cmd_query(request->query, &NameSpaceIndex, NodePath, &items, &type);
	WARNING2("当前参数：[%d],[%s],[%d]", NameSpaceIndex, NodePath.c_str(), type);

	if (name == NULL)
	{
		WARNING2("parse_opcua_cmd_query have error");
		goto end;
	}


	device = find_device_open(name);
	if (device == NULL)
	{
		WARNING2("find_device_open have error");
		goto end;
	}

	device_data = cJSON_CreateArray();
	switch (type)
	{
	case T_Bits:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_Boolean variableValue = *(UA_Boolean*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);

		}
	}
	break;
	case T_int8:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_SByte variableValue = *(UA_SByte*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_Uint8:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_Byte variableValue = *(UA_Byte*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_int16:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_Int16 variableValue = *(UA_Int16*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_Uint16:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_UInt16 variableValue = *(UA_UInt16*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_int32:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_Int32 variableValue = *(UA_Int32*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_Uint32:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_UInt32 variableValue = *(UA_UInt32*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_Float:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_Float variableValue = *(UA_Float*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_int64:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_Int64 variableValue = *(UA_Int64*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_Uint64:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				uint64_t variableValue = *(uint64_t*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	case T_Double:
	{
		for (int b = 0; b < items; b++)
		{
			UA_Variant value;
			UA_Variant_init(&value);
			res = UA_Client_readValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &value);
			if (res == 0)
			{
				UA_Double variableValue = *(UA_Double*)value.data;
				cJSON_AddItemToArray(device_data, cJSON_CreateNumber(variableValue));
			}

			UA_Variant_clear(&value);
		}
	}
	break;
	}

	if (res != UA_STATUSCODE_GOOD)
	{
		WARNING2("读取失败,返回值 [%X]", res);
		goto end;
	}

	payload = (const char*)cJSON_PrintUnformatted(device_data);
	wa_req_env_handover_payload(req_env, CONTENT_2_05, IA_APPLICATION_JSON, strlen(payload), &payload);
	cJSON_Delete(device_data);
	free(name);

	return;

end:
	wa_set_response_status(req_env, INTERNAL_SERVER_ERROR_5_00);
	if (name)
		free(name);
	if (device_data)
		cJSON_Delete(device_data);

	return;
}

void opcua_Put(restful_request_t* request, REQ_ENV_HANDLE req_env)
{
	WARNING2("Enter..");
	UA_StatusCode res;
	int code = BAD_REQUEST_4_00;
	if (request->query == NULL || request->payload_len == 0)
	{
		WARNING2("request is invalid");
		wa_set_response_status(req_env, code);
		return;
	}
	char* payload = request->payload;

	int NameSpaceIndex, items = 0;
	string NodePath;
	int type = T_Invalid_DataType;
	opcua_device* device;
	cJSON* rootvalues;


	char* name = parse_opcua_cmd_query(request->query, &NameSpaceIndex, NodePath, NULL, &type);

	WARNING2("当前参数：[%d],[%s],[%d]", NameSpaceIndex, NodePath.c_str(), type);

	UA_Variant newValue;
	UA_Variant_init(&newValue);

	auto writeFun = [&](auto tmp)
	{
		switch (type)
		{
		case T_Bits:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_BOOLEAN]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_int8:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_SBYTE]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_Uint8:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_BYTE]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_int16:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_INT16]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_Uint16:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_UINT16]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_int32:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_INT32]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_Uint32:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_UINT32]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_Float:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_FLOAT]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_int64:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_INT64]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_Uint64:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_UINT64]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		case T_Double:
		{
			UA_Variant_setScalar(&newValue, &tmp, &UA_TYPES[UA_TYPES_DOUBLE]);
			res = UA_Client_writeValueAttribute(device->dc, UA_NODEID_STRING(NameSpaceIndex, (char*)NodePath.c_str()), &newValue);
		}
		break;
		}
	};

	if (name == NULL)
	{
		WARNING2("parse_opcua_cmd_query have error");
		goto end;
	}

	device = find_device_open(name);
	if (device == NULL)
	{
		WARNING2("find_device_open have error");
		goto end;
	}



	if (request->payload_fmt == IA_TEXT_PLAIN)
	{
		if (type == T_Double)
		{
			double value2 = stod(payload);
			writeFun(value2);
		}
		else if (type == T_Float)
		{
			float value2 = stof(payload);
			writeFun(value2);
		}
		else
		{
			long long value2 = stoll(payload);
			writeFun(value2);
		}
	}
	else if (request->payload_fmt == IA_APPLICATION_JSON)
	{
		rootvalues = cJSON_Parse((const char*)payload);
		if (!cJSON_IsArray(rootvalues))
		{
			WARNING2("payload is not Array");
			goto end;
		}
		cJSON* item;
		cJSON_ArrayForEach(item, rootvalues)
		{
			if (!cJSON_IsNumber(item))
			{
				WARNING2("item is not Number");
				goto end;
			}
			if (type == T_Double)
			{
				double value2 = item->valuedouble;
				writeFun(value2);
			}
			else if (type == T_Float)
			{
				float value2 = static_cast<float>(item->valuedouble);
				writeFun(value2);
			}
			else
			{
				int value2 = item->valueint;
				writeFun(value2);
			}
		}
	}
	else
	{
		WARNING2("payload_fmt is invalid");
		goto end;
	}


	if (res == 0)
	{
		code = CHANGED_2_04;
	}
	else
	{
		WARNING2("写入失败,返回值 [%X]", res);
		code = INTERNAL_SERVER_ERROR_5_00;
		goto end;
	}

	free(name);
	cJSON_Delete(rootvalues);
	wa_set_response_status(req_env, code);
	return;

end:

	if (rootvalues != NULL)
		cJSON_Delete(rootvalues);
	if (name)
		free(name);

	wa_set_response_status(req_env, code);

	return;

}